Mit Hilfe von Bedingungen und Schleifen steuern Sie die Ausführung von Anweisungsblöcken in Perl-Skripten. Ohne solche Strukturen würde Ihr Perl-Skript einfach von oben nach unten durchlaufen und nacheinander alle Anweisungen abarbeiten, bis es zu Ende ist. Sie könnten nicht überprüfen, ob etwas einen bestimmten Wert hat, und dann gegebenenfalls zu einem anderen Teil des Codes abzweigen, Sie könnten nicht festlegen, ob und wie oft ein Anweisungsblock noch einmal ausgeführt wird - ohne Kontrollstrukturen wäre Perl sehr langweilig.
In dieser Lektion behandeln wir die verschiedenen Bedingungs- und Schleifenkonstrukte, die Sie für Ihre Perl-Skripts brauchen. Unsere heutigen Themen sind:
if
, if
...else
, if
...elsif
und unless
...
while
, do
...while
und until
for
und foreach
next
, last
, redo
und Labels
$_
als Abkürzung für viele Operationen
<>
aus Dateien lesen
Bedingungen und Schleifen werden manchmal auch komplexe Anweisungen
genannt, und zwar deshalb, weil Bedingungen und Schleifen im Vergleich zu
einzelnen Anweisungen, die mit einem Semikolon enden (wie zum Beispiel $x = 5;
oder $array[0] = "erstes";)
, wie soll ich sagen ... eben komplexer sind. Der
wahrscheinlich bedeutendste Unterschied zwischen einfachen und komplexen
Anweisungen ist allerdings, dass letztere mit ganzen Blöcken von Perl-Code arbeiten.
Ein Block ist einfach eine in geschweifte Klammern {}
gesetzte Folge von beliebigen
Perl-Anweisungen. Innerhalb eines Blocks können einfache Anweisungen oder andere
Blöcke stehen, also alles, was auch außerhalb des Blocks auftauchen kann. Wie
Anweisungen in einem Skript werden die Anweisungen in einem Block nacheinander
ausgeführt. Zum Beispiel:
while (Bedingung) { # Beginn des Blocks
Anweisung;
Anweisung;
if (Bedingung) { # Beginn des if-Blocks
Anweisung;
} # Ende des if-Blocks
# ... weitere Anweisungen
} # Ende des Blocks
Eine andere Eigenschaft von Blöcken ist, dass Perl hinter der letzten Anweisung im Block nicht unbedingt ein Semikolon verlangt. Es ist aber eine gute Idee, das Semikolon trotzdem zu setzen - dann müssen Sie nicht mehr darauf achten, wenn Sie später weitere Anweisungen hinzufügen.
Blöcke, die nicht an eine Bedingung oder Schleife gebunden sind, werden bare blocks oder freistehende Blöcke genannt und nur einmal ausgeführt. Bare blocks können durchaus nützlich sein, besonders wenn Sie sie mit einem Label versehen, doch jetzt werden wir uns erst einmal auf die Blöcke konzentrieren, die zu komplexen Anweisungen gehören.
Mit Hilfe von Bedingungsanweisungen können Sie festlegen, welche Blöcke Perl ausführen soll, wenn ein Ausdruck einen bestimmten Wert hat. Sie setzen eine Bedingung auf, und Perl überprüft, ob diese Bedingung erfüllt ist, das heißt, ob der Bedingungsausdruck wahr ist. Wenn der Ausdruck wahr ist, führt Perl den direkt folgenden Block aus, wenn er falsch ist, überspringt Perl diesen Block und macht beim nächsten Block oder Teil des Skripts weiter. Anders als bei Schleifen wird jeder Block nur einmal ausgeführt.
Die häufigste Formen der Bedingung sind if
(wenn) und seine Varianten if...else
(wenn...sonst) und if...elsif
(wenn...sonst wenn). Die if
-Anweisung sieht wie
folgt aus:
if ( Bedingungsausdruck) {
# Anweisungen
}
Bedingungsausdruck
kann jeder beliebige Ausdruck sein; er wird in Booleschem
skalaren Kontext auf seinen Wahrheitswert überprüft. Sie erinnern sich, dass alles
außer ""
, 0
und undef
als wahr angesehen wird. Ist der Bedingung
sausdruck wahr,
wird der Anweisungsblock ausgeführt. Ist er falsch, passiert gar nichts, und Perl macht
mit der nächsten Anweisung unter dem if
-Block weiter.
Beachten Sie, dass anders als in C oder Java auf die Bedingung (auch bei else
und
elsif
) immer ein Block folgen muss, auch wenn er nur eine einzige Anweisung
enthält. Sie müssen die geschweiften Klammern immer setzen - aber nicht unbedingt
in verschiedenen Zeilen, wie ich es eben gezeigt habe. Wo die Klammern stehen,
bleibt Ihrem Geschmack überlassen.
Um einen alternativen Block ausführen zu lassen, wenn die Bedingung nicht erfüllt ist,
verwenden Sie if
...else
:
if ( Bedingungsausdruck ) {
# Anweisungen, die ausgefuehrt werden,
# wenn Bedingungsausdruck wahr ist
} else {
# Anweisungen, die ausgefuehrt werden,
# wenn Bedingungsausdruck falsch ist
}
Wie in anderen Sprachen auch, können Sie if
und else
auch ineinander
verschachteln:
if ( Bedingungsausdruck 1 ) {
# Anweisung 1
} else {
if ( Bedingungsausdruck 2 ) {
# Anweisung 2
} else {
if ( Bedingungsausdruck 3 ) {
# Anweisung 3
} else {
# und so weiter...
}
}
}
Um Ihnen etwas Tipparbeit zu ersparen oder unübersichtlich viele Einzüge zu
vermeiden, bietet Perl eine dritte Form von if
-Bedingungen, das elsif
, das diese Art
von Operationen um einiges kompakter macht:
if ( Bedingungsausdruck 1 ) {
# Anweisung 1
} elsif ( Bedingungsausdruck 2 ) {
# Anweisung 2
} elsif ( Bedingungsausdruck 3 ) {
# Anweisung 3
} else (
# Anweisung fuer alle anderen Faelle
}
Beachten Sie, dass sobald die Bedingung eines elsif
wahr ist, alle noch folgenden
elsif
-Blöcke
übersprungen, das heißt nicht einmal ausgewertet werden. Bei
verschachtelten if...else
-Anweisungen ist es ebenso: In jeder Folge von Blöcken
wird jeweils nur der erste ausgeführt, dessen Bedingung erfüllt ist.
Was ist mit Fallunterscheidungskonstrukten wie switch
oder case
? Perl hat von sich
aus keine Anweisung für switch
(das ist wahrscheinlich das einzige Konstrukt, für das
andere Sprachen eine eigene Anweisung haben, Perl aber nicht). Allerdings gibt es
verschiedene Möglichkeiten, mit vorhandenen Perl-Konstrukten eine switch
-
Anweisung zu »simulieren«. Ein paar davon werden wir im Abschnitt »Vertiefung« am
Ende dieser Lektion durchgehen.
Die unless
-Anweisung ist eine Art umgekehrtes if
. Manchmal soll eine Operation
nur ausgeführt werden, wenn eine Bedingung nicht erfüllt ist - was bedeutet, dass in
einer normalen if...else
-Anweisung all das gute Zeug in den else
-Teil wandern
würde wie hier:
if ( Bedingungsausdruck ) {
# mache nichts
} else {
# mache das hier
}
Das ist nicht unbedingt die optimale Lösung. Sie könnten natürlich den
Bedingungsausdruck negieren (mit not
oder !
):
if (not Bedingungsausdruck ) {
# mache das hier
}
So müßten Sie es in anderen Sprachen machen und Ihre Denkweise an die Syntax
anpassen. Perl aber zieht es vor, wenn Sie so denken, wie Sie zu denken gewohnt
sind, und bietet Ihnen deshalb eine Alternative. Wenn Sie denken: »Mache das und
das, außer unter dieser Bedingung«, also eine Operation nur ausführen möchten,
wenn diese Bedingung nicht erfüllt ist, nehmen Sie einfach unless
(vom Englischen
unless, wenn nicht oder außer wenn):
unless ( Bedingungsausdruck ) {
# mache das hier
}
Mit diesem unless
wird der Block nur dann ausgeführt, wenn der
Bedingungsausdruck
falsch ist. Ist er aber wahr, wird der Block übersprungen. Sie
können einem unless
auch ein else
hinzufügen, wenn Sie möchten (aber kein elsif
,
und so etwas wie elsunless
gibt es zum Glück auch nicht).
Manche Bedingungen sind so kurz, dass es viel zuviel Aufwand wäre, all diese
Klammern und Worte auf sie zu verschwenden. Manchmal ist es sinnvoll, eine
Bedingung in einen anderen Ausdruck zu betten (was mit if
oder unless
nicht
möglich ist, weil diese keine Werte zurückliefern). Verwenden Sie in solchen
Situationen den Bedingungsoperator ?...:.... Wie bei if...else
brauchen Sie einen
Bedingungsausdruck und zwei Angaben - was Perl bei erfüllter und bei nicht erfüllter
Bedingung tun soll. Das Ganze schreiben Sie dann in folgender Form:
Bedingungsausdruck ? wert_wenn_wahr : wert_wenn_falsch;
Hier wird der Bedingungsausdruck auf seine Wahrheit überprüft, genau wie bei if
,
und wenn er wahr ist, dann wird der Ausdruck wert_wenn_wahr
ausgewertet (und der
Wert zurückgegeben), anderenfalls wird wert_wenn_falsch ausgewertet und
zurückgegeben. Anders als bei if
und unless
sind wert_wenn_wahr und
wert_wenn_falsch einzelne Ausdrücke, keine Blöcke. Zum Beispiel können Sie mit
der folgenden Zeile ganz schnell den höheren von zwei Werten herausfinden:
$max = $x > $y ? $x : $y;
Dieser Ausdruck sieht nach, ob der Wert von $x
größer ist als der von $y
. Wenn ja,
gibt er $x
zurück. Wenn $x
kleiner oder gleich $y
ist, gibt er $y
zurück. Der
zurückgegebene Wert wird dann der Variablen $max
zugewiesen. Mit if
und else
würde der gleiche Vorgang so aussehen:
if ($x > $y) {
$max = $x;
} else {
$max = $y;
}
Der Bedingungsoperator wird manchmal auch triadischer Operator genannt, weil er drei Operanden hat (monadische Operatoren haben einen, dyadische zwei und triadische drei Operanden).
Am Tag 2 habe ich Ihnen Perls logische Operatoren &&
, ||
, and
und or
erklärt und
kurz erwähnt, dass Sie damit Bedingungsanweisungen konstruieren können. Lassen
Sie uns diese Operatoren hier noch einmal betrachten, damit Sie ein Gefühl dafür
bekommen, wie sie arbeiten. Nehmen Sie den folgenden Ausdruck:
$wert = $dies || $das;
Um zu verstehen, wie dieser Ausdruck funktioniert, müssen Sie sich an zwei
Eigenschaften von logischen Operatoren erinnern: dass sie »kurzschließen« und dass
sie den Wert des zuletzt ausgewerteten Ausdrucks zurückgeben. So wird in dem
obigen Beispiel zuerst $dies
, der linke Operand von ||
, überprüft, und dann gibt es
drei Möglichkeiten:
$dies
irgendeinen anderen Wert als 0
oder ""
hat, ist der Ausdruck wahr.
Weil der ||
-Operator kurzschließt, wird $das
gar nicht mehr ausgewertet. Der
Operator gibt das Ergebnis der letzten Auswertung zurück - den Wert von $dies
,
der dann der Variablen $wert
zugewiesen wird.
$dies
den Wert 0
oder ""
, ist der Ausdruck falsch. Perl wertet dann $das
aus. Wenn $das
wahr ist, ist der gesamte Ausdruck wahr, und der Operator gibt
den zuletzt ermittelten Wert zurück - $wert
erhält den Wert von $das
.
$dies
und $das
, falsch sind, gibt der Operator falsch zurück,
und $wert
erhält einen 0
- oder ""
-Wert.
Mit if
...else
könnten Sie diese Anweisung wie folgt schreiben:
if ($dies) { $wert = $dies; }
else {$wert = $das; }
Mit dem Bedingungsoperator könnten Sie schreiben:
$wert = $dies ? $dies : $das
Aber beide brauchen mehr Platz und sind schwieriger zu begreifen - zumindest schwieriger als der logische Ausdruck, der sich fast wie Klartext liest: dies oder das. Und schon sind Sie fertig.
Wie ich am Tag 2 schon erwähnt habe, werden Ihnen solche für eine Entscheidung eingesetzten logischen Operatoren am häufigsten beim Öffnen von Dateien begegnen:
open(DATEI, "Dateiname") or die "Kann Datei nicht oeffnen\n";
Wenn die Funktion open
eine Datei erfolgreich geöffnet hat, gibt sie wahr zurück, und
deswegen wird der Teil nach dem or
übersprungen, die Funktion die
(»stirb«) also gar
nicht erst ausgeführt. Mehr hierzu am Tag 15.
Mit den verschiedenen if
-Anweisungen in Perl steuert man den Programmfluß,
indem man (je nachdem, ob eine Bedingung erfüllt ist oder nicht) zu verschiedenen
Teilen des Skripts abzweigt. Die zweite Steuerungsmöglichkeit sind Schleifen - die ein
und denselben Anweisungsblock immer wieder ausführen und erst dann aufhören,
wenn eine bestimmte Bedingung erfüllt ist. Perl hat zwei generelle Arten von
Schleifen, die beide ungefähr dasselbe machen: while
-Schleifen laufen solange, bis
eine Bedingung erfüllt ist, bei for
-Schleifen ist die Anzahl der Durchläufe von Anfang
an festgelegt. Man kann jede while
-Schleife auch als for
-Schleife formulieren und
umgekehrt, doch paßt je nach Situation meist die eine besser als die andere.
Wir beginnen mit den while
-Schleifen. Davon hat Perl drei verschiedene: while
,
do
...while
und until
.
Die Grundform einer Schleife in Perl ist die while
-Schleife
mit einem
Bedingungsausdruck und einem Anweisungsblock:
while ( Bedingungsausdruck ) {
# zu wiederholende Anweisungen
}
In der while
-Schleife wird der Bedingungsausdruck ausgewertet, und wenn er wahr
ist, werden die Anweisungen im Block ausgeführt. Danach wird der
Bedingungsausdruck wieder ausgewertet, und wenn er immer noch wahr ist, wird der
Anweisungsblock wieder ausgeführt. Dieser Vorgang wiederholt sich so lange, bis der
Bedingungsausdruck falsch ergibt. Als Beispiel zeige ich Ihnen noch einmal die while
-
Schleife aus dem Krümelmonster-Skript, das Sie bereits am ersten Tag gesehen
haben:
while ( $kekse ne "KEKSE") {
print 'ich will KEKSE: ';
chomp($kekse = <STDIN>);
}
Hier wiederholt sich der Eingabevorgang (die print-
und die <STDIN>
-Anweisung) so
lange, bis die Eingabe den String "KEKSE"
enthält. Sie könnten diese
Schleifenanweisung auch so lesen: »Solange der Wert von $kekse
nicht gleich dem
String "KEKSE"
ist, tue folgendes.«
Hier ein anderes Beispiel von Tag 4, das mit einer temporären Variablen $i
für den
Array-Index ein Array durchläuft:
$i = 0;
while ($i <= $#array) {
print $array[$i++], "\n";
}
In diesem Fall ist die Bedingung, dass $i
kleiner oder gleich dem größten Array-Index
ist. Innerhalb des Schleifenblocks geben wir das aktuelle Array-Element aus und
inkrementieren $i
. So bestimmen wir, wie oft die Schleife durchlaufen werden soll:
solange wie $i
kleiner oder gleich dem größten Index in @array
ist, also $#array
+ 1,
(weil wir ja bei 0 anfangen).
Beachten Sie beim Schreiben von while
-Schleifen, dass innerhalb der Schleife etwas
passieren muss, dass sie näher an ihr Ende bringt. Wenn Sie hier zum Beispiel
vergessen, $i
zu erhöhen, wird die Schleifenbedingung nie erfüllt, und die Schleife
läuft und läuft und läuft ohne Ende.
Schleifen, die nicht enden, werden Endlosschleifen genannt und können - wenn man
sie bewußt verwendet - durchaus von Nutzen sein. Zum Beispiel ist eine while
-
Schleife ohne Bedingung eine absichtlich endlose Schleife. Sie haben ein paar davon
in den bisherigen Beispielen gesehen. Diese hier ist aus den Statistikskripten:
while () {
print 'Bitte eine Zahl eingeben: ';
chomp ($input = <STDIN>);
if ($input ne '') {
$zahlen[$count] = $input;
$count++;
$sum += $input;
}
else { last; }
}
Diese Schleife liest bei jedem Durchlauf eine Zeile von der Standardeingabe und kann
niemals aufgrund einer Schleifenbedingung enden, da es keine Bedingung gibt. Doch
innerhalb der Schleife überprüfen wir die Eingabe mit if
...else
. Wenn $input
ein
leerer String ist, unsere if
-Bedingung also nicht erfüllt ist, wird der else
-Block
ausgeführt, der nur aus dem Schleifensteuerbefehl last
besteht. Dieses last
bricht die
Schleife ab und geht zum nächsten Teil des Skripts weiter. Es gibt in Perl drei solcher
Schleifensteuerbefehle: last
, next
und redo
, die wir später in diesem Kapitel, im
Abschnitt »Schleifen steuern«, genauer betrachten werden.
Ich hätte diese Schleife auch so schreiben können, dass das while
eine richtige
Bedingung hätte und zum richtigen Zeitpunkt abbrechen würde. In diesem speziellen
Beispiel fand ich es aber einfacher, die Schleife eben auf diese Art zu konstruieren.
Perl zwingt Ihnen auch bei Schleifen oder Bedingungen kein bestimmtes Denkmuster
auf, Sie können Ihr Skript so bauen, wie Sie es für die jeweilige Aufgabe am besten
halten.
Genau wie unless
das Gegenteil von if
ist, ist until
das Gegenteil von while
. Until
sieht genauso aus wie while
, mit einem Bedingungsausdruck und einem Block:
until ( Bedingungsausdruck ) {
# Anweisungen
}
Der einzige Unterschied ist die Bedeutung der Bedingung - mit while
wird die
Schleife so lange durchlaufen, wie die der Bedingungsausdruck wahr ist. Mit until
wird sie so lange ausgeführt, wie der Bedingungsausdruck falsch ist. Until ist englisch
und heißt bis - »Bis diese Bedingung wahr ist, tue das hier.«
Die dritte Form von while
-Schleifen ist do
. Bei while
und until
wird die
Schleifenbedingung ausgewertet, bevor der Block ausgeführt wird - wenn der
Bedingungsausdruck also von Anfang an falsch (oder bei unless
wahr) ist, bricht die
Schleife sofort ab und macht gar nichts. Manchmal aber möchten Sie einen
Anweisungsblock zunächst einmal ausführen und erst danach entscheiden, wie es
weitergeht. Hierfür gibt es do
. do
-Schleifen haben einen anderen Aufbau als while
-
und until
-Schleifen und sehen etwa wie folgt aus (beachten Sie das Semikolon am
Ende, bei do
ist es Pflicht):
do {
# Schleifenblock
} while (Bedingungsausdruck);
do {
# Schleifenblock
} until (Bedingungsausdruck);
Mit diesen beiden Anweisungen werden die Blöcke vor der Auswertung des
Bedingungsausdrucks ausgeführt. Selbst wenn der Bedingungsausdruck falsch (bei
while
) oder wahr (
bei until
) ist, wird der Anweisungsblock mindestens einmal
ausgeführt.
In Wirklichkeit ist
do
eine Funktion, die hier nur so tut, als wäre sie eine Schleife (deshalb brauchen Sie auch das Semikolon). Meistens verhältdo
sich genau wie eine Schleife, nur wenn Sie mit Schleifensteuerbefehlen (wielast
odernext
) oder mit Labels (Sprungmarken) arbeiten wollen, müssen Sie stattdo
eine »echte«while
- oderuntil
-Schleife verwenden. Schleifensteuerbefehle und Labels besprechen wir noch in dieser Lektion.
In diesem Beispiel spielen wir ein kleines Spiel. Perl bittet Sie um eine Zahl, »zieht« dann eine Zufallszahl zwischen 1 und Ihrer Zahl, und Sie sollen raten, welche es gezogen hat, zum Beispiel:
% zahlraten.pl
Geben Sie die hoechste Zahl ein: 50
Ihr Tipp? (eine Zahl zwischen 1 und 50): 25
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 10
Zu niedrig!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 17
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 13
Zu hoch!
Ihr Tipp? (eine Zahl zwischen 1 und 50): 12
Richtig!
Gratuliere! Sie haben mit 5 Versuchen die richtige Zahl erraten.
%
Das Skript dazu arbeitet mit zwei endlosen while
-Schleifen, einer Menge if
-
Bedingungen und der rand
-Funktion zur Ermittlung einer Zufallszahl. Listing 6.1 zeigt
den Code.
Listing 6.1: Das Skript zahlraten.pl
1: #!/usr/bin/perl -w
2:
3: $top = 0; # hoechste Zahl
4: $num = 0; # Zufallszahl
5: $count = 0; # zaehlt Versuche
6: $guess = ""; # aktueller Tipp
7:
8: while () {
9: print 'Geben Sie die hoechste Zahl ein: ';
10: chomp($top = <STDIN>);
11: if ($top == 0 || $top eq '0') {
12: print "Das ist keine gute Zahl.\n";
13: }
14: else { last; }
15: }
16:
17: srand;
18: $num = int(rand $top) + 1;
19:
20: while () {
21: print " Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
22: chomp($guess = <STDIN>);
23: if ($guess == 0 || $guess eq '0') {
24: print "Das ist keine gute Zahl.\n";
25: } elsif ($guess < $num) {
26: print "Zu niedrig!\n";
27: $count++;
28: } elsif ($guess > $num) {
29: print "Zu hoch!\n";
30: $count++;
31: } else {
32: print "\a\aRichtig! \n";
33: $count++;
34: last;
35: }
36: }
37: print "Gratuliere! Sie haben mit $count Versuchen";
38: print " die richtige Zahl erraten.\n";
Dieses Skript hat vier Teile: Initialisierung, Eingabe der höchsten Zahl, Auswahl der zu ratenden Zahl und dann das Raten selbst. Den Initialisierungsteil überspringe ich diesmal ganz; Sie werden mittlerweile wissen, wie man Skalarvariablen zuweist.
In Zeile 8 bis 15 erfragen wir, wie hoch die Zufallszahl höchstens sein darf. Diese
while
-Schleife ist endlos, doch die Bedingung in Zeile 11 soll sicherstellen, dass Sie
nicht 0 eingeben. Tun Sie es doch, wird eben nicht das last
im else
-Block, sondern
die Eingabeschleife von vorn ausgeführt. Denken Sie daran, dass Perl bei der
Umwandlung von Strings in Zahlen einen String in 0 konvertiert, wenn es keine
numerischen Daten in ihm findet. Deswegen fängt diese Bedingung sowohl eine 0 als
auch alle anderen nichtnumerischen Eingaben ab. Perl-Warnungen würden sich bei
der Eingabe eines Strings trotzdem über »not a number
« (»keine Zahl«) beschweren.
Wenn wir uns in ein paar Tagen mit Mustervergleichen (Pattern Matching) befassen,
werden Sie einen besseren Weg kennenlernen, diese Warnungen zu umgehen.
Jedenfalls generieren wir, sobald wir eine brauchbare Zahl haben, in Zeile 17 und 18
die geheime Zahl, und zwar mit den eingebauten Funktionen srand
und rand
. Ohne
Argumente setzt die srand
-Funktion den Zufallsgenerator auf die aktuelle Uhrzeit,
damit wir jedesmal andere Zahlen bekommen (ansonsten wäre es in der Tat ein sehr
langweiliges Spiel). Die Funktion rand
generiert dann eine Zufallszahl zwischen 0 und
einem Argument, hier unserem $top
(einschließlich 0 ohne $top
). Wir schneiden diese
Zahl auf einen Integer zu, addieren 1
, damit wir keine Nullen, aber gelegentlich auch
die eingegebene Höchstzahl erhalten, und speichern sie in der Variablen $num
.
In den Zeilen 20 bis 34 wird geraten. Hier überprüfen wir mit if
...elsif
-
Anweisungen drei Dinge:
Wenn der Vorschlag weder zu niedrig noch zu hoch, aber eine gültige Zahl ist, dann
muss es die richtige Zahl sein - also piepen wir zweimal (okay, Sie vielleicht nicht,
aber die Escape-Sequenz \a
gibt einen Piepton aus), Perl schreibt einen Glückwünsch
auf den Bildschirm und hört auf.
Während der Benutzer Zahlen rät, merken wir uns die Anzahl der Versuche in einer
$count
-Variablen. Wir wollen aber nur gültige Versuche zählen, also inkrementieren
wir $count
nur in den drei Fällen gültiger Zahlen (zu niedrig, zu hoch, richtig). $count
wird dann mit der Glückwunschmeldung ausgegeben.
Geht es um die Wiederholung eines Anweisungsblocks, bietet while
als »Mutter aller
Schleifen« immer einen Weg - sie führt einen Block immer wieder aus, und zwar so
lange (bzw. bis) die Schleifenbedingung erfüllt ist. Die zweite Art von Schleife, die for
-
Schleife, löst das gleiche Problem auf etwas andere Art und Weise. Bei for
-Schleifen
wird der Anweisungsblock n-mal hintereinander ausgeführt, dann wird die Schleife
beendet.
Sie könnten jede while
-Schleife auch als for
-Schleife formulieren und umgekehrt.
Aber für manche Aufgaben bietet sich die eine Form eher an als die andere.
Perl kennt zwei for
-Schleifen: eine allgemeine C-ähnliche for
-Schleife und eine dem
Shell-Skripting entliehene foreach
-Schleife, die einen Block »für jedes« (for each)
Element einer Liste wiederholt.
Die for
-Schleife ist in Perl dieselbe wie in C. Sie setzen eine Lauf- oder Zählervariable
(zum Beispiel $i)
auf einen beliebigen Startwert, überprüfen eine Bedingung, führen
den Schleifenblock aus, wenn diese erfüllt ist, ändern dann den Wert der
Laufvariablen, überprüfen wieder die Bedingung, durchlaufen die Schleife
gegebenenfalls von neuem, ändern wieder die Laufvariable - und so weiter:
for ( Startwert; Bedingungsausdruck; Aenderung ) {
# Anweisungen
}
In diesem Beispiel ist Startwert
der Ausdruck zum Initialisieren der Laufvariablen, der
Bedingungsausdruck
entscheidet, ob die Iteration weitergeht, und Aenderung
bestimmt, wie der Wert der Laufvariablen nach jedem Schleifendurchlauf geändert
wird. Beim ersten Durchlauf wird der Zähler initialisiert, die Bedingung ausgewertet
und, wenn sie wahr liefert, der Anweisungsblock ausgeführt. Beim zweiten Durchlauf
wird die Änderungsanweisung ausgeführt, die Bedingung wieder überprüft und wenn
sie immer noch wahr ist, der Block wieder ausgeführt. So geht es immer weiter, bis
der Bedingungsausdruck ein falsch zurückgibt.
Für fünf Schleifendurchläufe könnten Sie zum Beispiel eine for
-Schleife wie diese
verwenden:
for ( $i = 1; $i <= 5; $i++) {
print "Durchlauf $i\n";
}
Dieser Code-Schnipsel produziert folgende Ausgabe:
Durchlauf 1
Durchlauf 2
Durchlauf 3
Durchlauf 4
Durchlauf 5
Sie könnten diese Schleife selbstverständlich auch als while
-Schleife schreiben:
$i = 1;
while ($i <= 5) {
print "Durchlauf $i\n";
$i++;
}
Beide Schleifen würden fünfmal durchlaufen, doch die for
-Schleife ist kompakter und
übersichtlicher. Sie listet sozusagen fein säuberlich auf, wo sie anfängt, wo sie aufhört
und welche Schritte sie dazwischen unternimmt. Für manche Aufgaben eignet sie sich
besser als eine while
-Schleife - beispielsweise, wenn Sie einen begrenzten Satz von
Werten durchgehen, etwa zum Bearbeiten aller Elemente in einem Array.
Sie können die Initialisierung, den Bedingungsausdruck oder die Änderungsanweisung
oder alle drei im oberen Teil der for
-Schleife weglassen, nicht jedoch die Semikola.
Eine endlose for-
Schleife könnte zum Beispiel folgendermaßen aussehen:
for ($i = 0; ; $i++) {
# Anweisungen
}
for (;;) {
# Anweisungen
}
Die erste Schleife initialisiert den Schleifenzähler $i
und erhöht ihn nach jedem
Durchlauf um 1. Die zweite Schleife hat nicht einmal eine Zählervariable, sie wird
einfach nur durchlaufen. Um aus diesen Schleifen herauszukommen, müssen Sie die
Abbruchbedingung innerhalb der Blöcke festlegen.
Sie wollen die Schleife über mehrere Zähler laufen lassen? Setzen Sie einfach Kommas zwischen Ihre Zählerausdrücke:
for ($i=0, $j=1; $i < 10, j$ < $total; $i++, $j++ ) {
# Anweisungen
}
In diesem Fall wird die Schleife abgebrochen, wenn einer der beiden Bedingungsausdrücke wahr zurückgibt.
Die for
-Schleife bietet sich an, wenn Sie auf einen konkreten Endpunkt überprüfen
können - zum Beispiel den höchsten Index in einem Array oder eine bestimmte Zahl.
foreach
ist eine Kurzversion von for
ohne expliziten Zähler. foreach
durchläuft eine
Liste und führt den Anweisungsblock so viele Male aus, wie diese Liste Elemente hat.
Sie kennen foreach
bereits - gestern haben wir uns damit durch Listen von Hash-
Schlüsseln gearbeitet. Hier nun die ausführliche »Gebrauchsanweisung« für foreach
:
Sie übergeben der foreach
-Schleife eine Liste - zum Beispiel eine Liste aller Schlüssel
in einem Hash oder einen Bereich. foreach
durchläuft die Liste, setzt dabei eine
temporäre Variable nacheinander auf den Wert jedes Elements und führt für jedes
Element in der Liste den Anweisungsblock aus.
Hier ein simples Äquivalent zur for
-Schleife von vorhin, die den Zähler fünfmal
ausgegeben hat:
foreach $zahl (1 .. 5) {
print "Durchlauf $zahl\n";
}
Hier erstellt der Bereichsoperator ..
eine Liste der Zahlen 1 bis 5, und die foreach
-
Schleife arbeitet sich durch diese Liste, wobei sie die Zahlen nacheinander der
Variablen $zahl
zuweist.
Foreach
-Schleifen eignen sich außerordentlich gut zum Durchlaufen von Listen, zum
Beispiel um die Schlüssel und Werte eines Hash auszugeben, wie Sie gestern gelernt
haben:
foreach $key (sort keys %hashname) {
print "Schlüssel: $key Wert: $hashname{$key}\n";
}
Wie Sie sehen, habe ich die $key
-Variablen hier nicht initialisiert (auch die $zahl
-
Variable im vorangehenden Beispiel wurde nicht initialisiert). Das ist nicht nötig, weil
die Variable zwischen foreach
und der Liste eine lokale Variable innerhalb der
Schleife ist - das heißt, vor und nach der Schleife existiert sie gar nicht. Falls sie doch
schon existiert, Sie also vor der Schleife eine gleichnamige Variable verwenden,
benutzt foreach
sie nur vorübergehend und stellt ihren ursprünglichen Wert wieder
her, sobald die Schleife verlassen wird. Sie können sich die foreach
-Variable als eine
Art »Kritzel«-Variable vorstellen, die einzig und allein dem kurzen Speichern von
Elementen dient und beim Schleifenende weggeworfen wird.
Wenn Sie die Werte der Listenelemente gar nicht brauchen, sondern lediglich die Liste durchgehen möchten, können Sie diese Variable auch einfach weglassen.
In while
- und for
-Schleifen stehen die Bedingungen, unter denen die Schleife
durchlaufen bzw. abgebrochen werden soll, ganz am Anfang. In vielen Fällen ist das
auch genau das Richtige. Wenn Sie aber mit komplexeren Schleifen arbeiten oder mit
Endlosschleifen spielen, wie ich es in einigen der bisherigen Beispiele getan habe,
möchten Sie vielleicht auch mitten in der Schleife entscheiden können, wie es von
diesem Punkt aus weitergehen soll. Und natürlich hat Perl da etwas für Sie:
Schleifensteuerbefehle.
Wie ich schon erwähnt habe, können Sie in
do
-Schleifen keine Schleifensteuerbefehle oder Labels (Sprungmarken) einsetzen. Dafür müßten Sie diedo
-Schleife zu einerwhile
- ,until
- ,for
- oderforeach
-Schleife umschreiben.
Mit Schleifensteuerbefehlen steuern Sie den Ablauf einer Schleife. Zwei von ihnen
haben Sie bereits gesehen: next
und last
, um die Schleife neu zu starten oder sie
komplett abzubrechen. Außerdem gibt es in Perl redo
und Labels, mit denen Sie Ihren
Schleifen einen Namen geben können.
Die Grundbausteine der Schleifensteuerung sind die Schlüsselwörter last
, next
und
redo
. Sobald eins von ihnen in einer while
- oder for
-Schleife auftritt, unterbricht Perl
die normale Schleifenausführung.
Sie können jeden dieser drei Befehle mit einem Label oder für sich allein verwenden. Mit einem Label beziehen sie sich auf die mit diesem Label versehene Schleife ohne Label auf die innerste Schleife (mehr über Labels später). Im einzelnen bewirken sie folgendes:
last
wird die Schleife sofort abgebrochen und verlassen (wie bei break
in C).
Der nächste Teil des Skripts wird ausgeführt.
next
wird der aktuelle Durchlauf abgebrochen, es wird zurück zum
Schleifenanfang gesprungen, und der nächste Durchlauf wird mit der
Überprüfung der Bedingung gestartet. Die Anweisung entspricht dem continue
-
Befehl in C. Das Schlüsselwort next
ist ein bequemer Weg, unter bestimmten
Bedingungen den Code im Rest des Anweisungsblocks zu überspringen.
redo
wird der aktuelle Schleifendurchlauf abgebrochen und wieder an den
Anfang gegangen. Der Unterschied zwischen redo
und next
ist, dass next
den
Bedingungsausdruck am Schleifenanfang noch einmal auswertet (und bei for
-
Schleifen die Änderungsanweisung ausführt). Redo
hingegen startet erst am
Anfang des Blocks neu, ohne den Bedingungsausdruck am Schleifenanfang noch
einmal auszuwerten. Sie können sich den Unterschied so vorstellen, dass redo
denselben Durchlauf noch einmal wiederholt, während next
zum nächsten springt
(und das ist tatsächlich exakt das, was passiert).
Schauen wir uns noch einmal das Zahlenratespiel an. Wir könnten die zweite while
-
Schleife auch so schreiben:
while () {
print "Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
chomp($guess = <STDIN>);
if ($guess == 0 || $guess eq '0') { # wenn Eingabe 0 oder String
print "Das ist keine gute Zahl.\n";
next;
}
if ($guess < $num) { # wenn Eingabe zu niedrig
print "Zu niedrig!\n";
$count++;
next;
}
if ($guess > $num) { # wenn Eingabe zu hoch
print "Zu hoch!\n";
$count++;
next;
}
$count++;
last;
}
Weil diese while
-Schleife endlos ist, müssen wir mit Schleifensteuerbefehlen innerhalb
des Schleifenkörpers festlegen, wann sie abgebrochen werden soll - in unserem Fall,
wenn die richtige Zahl eingegeben wurde. In dem Schleifenblock wird die Eingabe auf
drei verschiedene Dinge überprüft: ob sie gleich 0 ist, ob sie kleiner als die geheime
Zahl ist oder ob sie größer als die geheime Zahl ist. In jeder dieser if
-Anweisungen
überspringen wir, wenn eins dieser Kriterien zutrifft, mit next
sofort die restlichen
Anweisungen im Block und gehen zurück zum Schleifenanfang (stünde dort ein
Bedingungsausdruck, würden wir ihn jetzt aufs neue auswerten). Wenn die
eingegebene Zahl allen drei Überprüfungen standhält, dann ist es die richtige, und wir
können die Schleife mit last
abbrechen.
Im allgemeinen sind Schleifensteuerbefehle nur notwendig, wenn Sie innerhalb der Schleife auf bestimmte Bedingungen überprüfen, die den normalen Ablauf der Schleife unterbrechen sollen, oder um aus einer Endlosschleife herauszukommen.
Schleifensteuerbefehle ohne Labels beziehen sich auf die innerste Schleife, das heißt,
sie unterbrechen die sie unmittelbar umschließende Schleife. Manchmal haben Sie
aber mehrere ineinandergeschachtelte Schleifen und möchten unter einer bestimmten
Bedingung aus mehreren Schleifen aussteigen. Aus diesem Grund können Sie
Schleifen mit Labels kennzeichnen und dann mit last
, next
und redo
zu diesen
Schleifen springen.
Labels stehen am Anfang der Schleife und werden vereinbarungsgemäß großgeschrieben, damit man sie nicht mit Perl-Schlüsselwörtern durcheinanderbringt. Verwenden Sie einen Doppelpunkt, um das Label von der Schleife zu trennen:
LABEL: while (Bedingungsausdruck) {
#...
}
Innerhalb der Schleife verwenden Sie dann last
, next
oder redo
mit dem Label:
LABEL: while (Bedingung_1) {
#...
while (Bedingung_2) {
# ...
if (noch_eine_Bedingung) {
last LABEL;
}
}
}
Ihre Labels können Sie nennen, wie Sie wollen, mit zwei Ausnahmen: BEGIN
und END
sind für die Paketkonstruktion und -dekonstruktion reserviert. Mit Paketen (englisch
Packages) werden wir uns in diesem Buch nicht befassen; lediglich am Tag 13 werde
ich Ihnen erklären, was ein Paket überhaupt ist. Wenn Sie bereit sind, in
fortgeschrittene Perl-Konzepte einzusteigen, schauen Sie in der perlmod-Manpage
nach weitergehenden Informationen über Pakete und Module.
Hier ein einfaches Beispiel ohne Labels. Die äußere while
-Schleife überprüft, ob die
Variable $exit
ungleich dem String 'n'
ist. Die innere while
-Schleife ist eine
Endlosschleife.
while ($exit ne 'n') {
while () {
print 'Geben Sie eine Zahl ein: ';
chomp($zahl = <STDIN>);
unless ($zahl eq '0' ) { # die Zahl 0 ist erlaubt
if ($zahl == 0 ) { # wenn Eingabe ein String
print "Keine Strings. Zahlen von 0 bis 9, bitte.\n";
next;
}
}
# andere Anweisungen
last;
}
print 'Eine weitere Zahl ausprobieren (j/n)?: ';
chomp ($exit = <STDIN>);
}
In diesem Beispiel verlassen next
und last
die sie unmittelbar einschließende Schleife
- also die innere, endlose Schleife, in der Sie die Zahl eingeben. Die äußere Schleife
wird nur in einem einzigen Fall verlassen: wenn Sie $exit
am Ende der äußeren
Schleife auf 'n'
setzen.
Sagen wir, Sie wollten diesem Skript noch die Möglichkeit hinzufügen, dass man mit
der Eingabe von exit
alle Schleifen und das Skript beenden kann. Dazu würden Sie
vor die äußere Schleife ein Label setzen:
AUSSEN: while ($exit ne 'n') {
# etc.
}
In der inneren Schleife würden Sie dann last
mit dem Label verwenden:
AUSSEN: while ($exit ne 'n') {
while () {
print 'Geben Sie eine Zahl ein (Beenden mit exit): ';
chomp($zahl = <STDIN>);
if ($zahl eq "exit" ) { # schluss, aus, vorbei!
last AUSSEN;
}
# etc.
}
# mehr...
}
Wenn der Benutzer hier am 'Geben Sie eine Zahl ein'
- Prompt »exit« eintippt,
bezieht sich der last
-Befehl auf die Schleife mit dem Label AUSSEN
- beendet also in
diesem Fall die äußere Schleife.
Beachten Sie, dass Schleifensteuerbefehle sich auf Schleifen beziehen und nicht auf
eine bestimmte Position im Skript (wie etwa goto
, falls Ihnen das etwas sagt). Am
besten stellt man sich die Labels weniger als Sprungmarken, als vielmehr als
Bezeichner für Schleifen, die man anspringen kann, vor. Wenn Sie mit einem
Schleifensteuerbefehl aus einer Schleife herausspringen, springen Sie in Wirklichkeit
nicht zu dem Label, sondern zur Anweisung direkt hinter der Schleife mit dem Label.
Glückwunsch! Sie wissen jetzt so gut wie alles, was Sie über Schleifen in Perl wissen
müssen (das übrige erfahren Sie im Abschnitt »Vertiefung«). Weil wir in dieser Lektion
noch etwas Platz haben, beende ich sie heute mit zwei allgemeinen Themen: der
speziellen Variablen $_
und einer einfachen Methode, Dateien zu lesen. Beides
werden wir in den weiteren Kapiteln dieses Buches häufig gebrauchen.
Fangen wir mit der $_
-Variablen an. Diese spezielle Variable können Sie sich als einen
Standardplatzhalter für skalare Werte vorstellen. Viele Perl-Konstrukte verwenden $_
,
wenn Sie keine eigene Skalarvariable angeben. Sie können das ausnutzen und Ihre
Skripts kürzer und effizienter machen - manchmal allerdings auf Kosten der
Lesbarkeit.
Betrachten wir ein Beispiel: foreach
. Sie erinnern sich, dass die foreach
-Schleife mit
einer temporären Variablen arbeitet, in der die Listenelemente nacheinander
zwischengespeichert werden. Wenn Sie keine temporäre Variable angeben, speichert
Perl die Werte in $_
. Innerhalb der Schleife können Sie sich dann auf $_
beziehen,
um an diesen Wert heranzukommen. Statt
foreach $key (sort keys %hash) {
print "Schlüssel: $key Wert: $hash{$key}\n";
}
können Sie genausogut schreiben:
foreach (sort keys %hash) {
print "Schlüssel: $_ Wert: $hash{$_}\n";
}
Auch viele Funktionen arbeiten mit dem Wert von $_
, wenn Sie ihnen keine
Argumente übergeben - print
und chomp
zum Beispiel. Wundern Sie sich also nicht
über Anweisungen wie:
print;
Fügen Sie einfach in Gedanken das $_
hinzu:
print $_;
Wir werden uns die $_
-Variable noch genauer ansehen - im nächsten Abschnitt und
am Tag 9.
In den Beispielen der letzten Tage haben wir Eingabedaten mit <STDIN>
von der
Tastatur gelesen. Wir haben den Datei-Handle für die Standardeingabe sowohl in
skalarem als auch in Listenkontext verwendet, und der Unterschied zwischen <STDIN>
in skalarem Kontext (Zeile für Zeile) und Listenkontext (bis zum Dateiendezeichen)
sollte Ihnen halbwegs klar sein.
Bestehen Ihre Eingabedaten allerdings aus mehr als nur ein paar Zeilen, ist es äußerst mühsam, sie alle über die Tastatur einzutippen. Das Statistikskript von gestern ist ein gutes Beispiel dafür - es dauert eine ganze Weile, alle Zahlen einzugeben, und wollten wir auch nur einen einzigen Wert hinzufügen, müßten wir komplett von vorne anfangen.
Viel praktischer wäre es, die Daten in eine eigene Datei zu speichern und von dort zu
lesen, wenn das Skript ausgeführt wird. Dafür gibt es in Perl zwei Möglichkeiten: Zum
einen können Sie eine bestimmte Datei innerhalb Ihres Skripts öffnen und lesen.
Diesem Thema habe ich eine ganze Lektion gewidmet, nämlich Tag 15. Es gibt aber
noch einen schnelleren Weg, Daten aus beliebigen Daten in ein Perl-Skript
einzulesen. Diese Technik, die auf dem Perl-Befehl line
beruht, bringe ich Ihnen
heute bei.
Wir haben den Zeileneingabeoperator <>
bis jetzt immer in Zusammenhang mit dem
Datei-Handle <STDIN>
verwendet. Wenn Sie <>
aber ohne ein Datei-Handle einsetzen,
holt Perl sich den Input aus der Datei, die Sie in der Kommandozeile angeben. Ein <>
ohne Datei-Handle bedeutet im Grunde nichts anderes als: »Nimm alle in der
Kommandozeile genannten Dateien, öffne sie, verkette sie miteinander und lies sie,
als wären sie eine einzige Datei.«
Genaugenommen entnimmt Perl die Namen der zu öffnenden und zu lesenden Dateien von einer speziellen Variablen namens
@ARGV
, einem Array mit Dateinamen oder anderen in der Kommandozeile angegebenen Werten, und Sie könnten@ARGV
im Skript noch verändern. Aber fürs erste nehmen wir einfach an, dass@ARGV
die in der Kommandozeile angegebenen Dateinamen enthält und<>
mit diesen arbeitet. Mehr über@ARGV
erfahren Sie an Tag 15.
Hier ein Beispiel, das die in der Kommandozeile angegebenen Dateien zeilenweise einliest und jede Zeile nacheinander ausgibt:
while (defined($input = <>)) {
print "$input";
}
Wenn Sie mit MacPerl arbeiten, haben Sie vielleicht gar keine Kommandozeile und wissen nicht recht, was Sie jetzt machen sollen. Aber keine Angst, es geht auch in MacPerl: Speichern Sie Ihr Skript als Droplet (diese Option finden Sie im Save-Dialog, Menü Type). Ist Ihr Skript als Droplet gespeichert, können Sie die zu lesenden Dateien per Drag & Drop auf das Skript-Icon ziehen - MacPerl wird starten und diese Dateien in das Skript lesen.
Sagen wir, Sie hätten dieses Beispiel als echodatei.pl
gespeichert und möchten die
Datei eine_Datei.txt
ausgeben. Von der Kommandozeile rufen Sie das Skript
folgendermaßen auf:
% echodatei.pl eine_Datei.txt
Wenn Sie mehrere Dateien ausgeben wollen, hängen Sie einfach alle anderen Dateinamen hinten an die Kommandozeile an:
% echodatei.pl eine_Datei.txt noch_eine_Datei.txt Datei_3.txt
Perl wird all diese Dateien öffnen und eine nach der anderen ausgeben.
Gehen wir diese while
-Schleifenbedingung noch einmal von innen nach außen durch,
damit Sie verstehen, was hier vor sich geht. Das $input = <>
wird Ihnen vertraut
vorkommen; es ist ähnlich wie beim Lesen einer Zeile mit <STDIN>
in skalarem
Kontext. Vielleicht erinnern Sie sich noch, dass die defined
-Funktion wahr oder
falsch zurückgibt, je nachdem, ob ein Argument definiert ist oder nicht (das heißt,
nicht den undefinierten Wert enthält). Hier verwenden wir defined
, um die Schleife
beim Dateiende abzubrechen - für jede Zeile der Datei bekommen wir einen gültigen
Wert, doch am Ende der Datei liefert <>
undefiniert zurück - $input
wird ebenfalls
undefiniert, die defined
-Funktion liefert falsch, und die while
-Schleife wird gestoppt.
Rein technisch gesehen brauchen Sie den
defined
-Teil gar nicht. Perl versteht auch so, wo das Dateiende ist, und hört automatisch auf zu lesen. Wenn Sie aber die Warnungen eingeschaltet haben, beschwert Perl sich, dass Sie nicht ausdrücklich auf das Dateiende geprüft haben. Sie können beides umgehen, die Warnung und den Aufruf vondefined
, wenn Sie$_
als Eingabevariable nehmen (siehe unten).
Wie bei <STDIN>
können die leeren spitzen Klammern sowohl in skalarem als auch im
Listenkontext verwendet werden. In skalarem Kontext lesen Sie die Eingabedateien
zeilenweise ein (wobei das Zeilenende ein Zeilenvorschub oder - auf dem Mac - ein
Carriage Return ist). Im Listenkontext wird jede Zeile der Datei (bzw. der Dateien) als
ein Element der Liste gespeichert.
Eine noch kürzere Version des echodatei
-Skripts macht von der $_
-Variablen
Gebrauch. Sie können die Variable $input
durch $_
ersetzen und auf die temporäre
Variable und die defined
-Funktion komplett verzichten:
while (<>) {
print;
}
Wenn die Bedingung einer while
-Schleife nichts als einen Zeileneingabeoperator
enthält, liest diese while
-Schleife die Eingabedateien Zeile für Zeile ein, weist jede
Zeile nacheinander der Variablen $_
zu und bricht ab, wenn <>
undefiniert ist, ohne
dass Sie darauf ausdrücklich überprüfen müßten - Sie erhalten deswegen keine
Fehlermeldung. Diesen Mechanismus können Sie eigentlich mit jeder Eingabequelle
verwenden - es funktioniert auch mit <STDIN>
oder einem anderen Datei-Handle.
Beachten Sie allerdings, dass es die while
-Schleife ist - nicht der <>
-Operator -, die
die Zeilen nacheinander $_
zuweist und auf das Dateiende überprüft. Sie können zum
Beispiel nicht do chomp(<>)
schreiben; dieser Funktionsaufruf würde die aktuelle Zeile
nicht in $_
speichern. Dieses besondere Feature haben nur while
- und for
-Schleifen
(for
-Schleifen werden als »verkleidete« while
-Schleifen betrachtet).
Dieser Mechanismus ist ein sehr gebräuchlicher Weg, Daten in ein Perl-Skript einzulesen. In vielen Perl-Skripten werden Ihnen gleich am Skript-Anfang solche Schleifen begegnen, die Daten aus Dateien in ein Array oder einen Hash speichern.
Betrachten wir ein weiteres Beispiel für den Einsatz von <>
und $_
zum Einlesen von
Daten. Gestern hatten wir ein Skript, das den Benutzer um die Eingabe von ein paar
Namen bittet und diese dann in ein Array speichert. Die Eingabeschleife dieses Skripts
sah wie folgt aus:
while () {
print 'Geben Sie einen Namen ein (Vor- und Nachname): ';
chomp($in = <STDIN>);
if ($in ne '') {
($fn, $ln) = split(' ', $in);
$namen{$ln} = $fn;
}
else { last; }
}
Jetzt möchten wir diese Namen aus einer Datei einlesen. Zu diesem Zweck streichen
wir die Eingabeaufforderung, den Aufruf von <STDIN>
und die Überprüfung auf eine
leere Eingabe:
while (defined($in = <>)) {
chomp($in);
($fn, $ln) = split(" ", $in);
$namen{$ln} = $fn;
}
Mit Hilfe der $_
-Variablen können wir das Skript noch weiter kürzen:
while (<>) {
chomp;
($fn, $ln) = split(' ');
$namen{$ln} = $fn;
}
Obwohl sie kein einziges Mal explizit auftaucht, wird die $_
-Variable hier sehr viel
verwendet: Die while
-Schleife weist ihr eine Zeile aus der Input-Datei zu und
überprüft, ob dieser Wert definiert ist. Dann greift chomp
auf diesen Wert zu und
schneidet den Zeilenvorschub ab. Und split
sieht in $_
nach, was es denn splitten
soll. Derartige Abkürzungen sind in Perl-Skripten sehr häufig.
Wir könnten sogar die split
-Funktion noch weiter abkürzen und das leere Argument
weglassen. Ohne Argumente trennt split
den String in $_
an den Leerstellen:
($fn, $ln) = split;
Wie in den bisherigen Lektionen habe ich Ihnen auch zu Bedingungen und Schleifen noch nicht alles gesagt. Zum Teil möchte ich dies jetzt nachholen und Ihnen einige der bisher ausgelassenen Konzepte vorstellen. Ansonsten steht es Ihnen natürlich frei, auf eigene Faust weiterzuforschen.
Alle Bedingungen und Schleifen, die Sie heute kennengelernt haben, sind (mit
Ausnahme von do
) komplexe Anweisungen - sie operieren auf Blöcken aus anderen
Anweisungen und benötigen kein Semikolon am Zeilenende. Daneben besitzt Perl
auch einen Satz sogenannter Modifikatoren (Modifier) für einfache Anweisungen, mit
denen Sie bedingungs- und schleifenähnliche Anweisungen erstellen können.
Manchmal helfen Modifikatoren, einfache Bedingungen und Schleifen noch kürzer
auszudrücken oder die Logik einer Anweisung besser darzustellen.
Es gibt vier mögliche Modifikatoren: if
, unless
, while
und until
. Jeden dieser
Modifier können Sie an eine einfache Anweisung anhängen, direkt vor dem
Semikolon. Hier ein paar Beispiele:
print "$wert" if ($wert < 10);
$z = $x / $y if ($y > 0);
$i++ while ($i < 10);
print $wert until ($wert++ > $maxwert)
Mit einem Bedingungsmodifikator (if
oder unless
) wird der vordere Teil der
Anweisung nur ausgeführt, wenn der Bedingungsausdruck wahr (oder bei unless
falsch) ist. Mit einem Schleifenmodifikator (while
oder until)
wird die Anweisung so
lange ausgeführt, wie der Ausdruck wahr (bzw. bei until
falsch) ist.
Beachten Sie, dass Anweisungen mit while
- oder until
-Modifikatoren nicht dasselbe
sind wie normale while-
oder until
-Schleifen. Sie können sie weder mit
Schleifensteuerbefehlen wie next
oder last
kontrollieren noch ihnen Schleifenlabels
zuweisen.
Die do
-Schleifen, die Sie vorhin in diesem Abschnitt kennengelernt haben, sind genau
genommen Anweisungen mit Schleifenmodifikatoren. Do
ist eine Funktion, die einen
Anweisungsblock ausführt. Mit den Modifikatoren while
und until
bestimmen Sie,
wie oft diese Ausführung wiederholt wird. Deshalb können Sie in do
-Schleifen keine
Schleifensteuerbefehle verwenden.
Ein continue
-Block ist ein optionaler Anweisungsblock nach einer Schleife, der
ausgeführt wird, wenn der Schleifenblock zu Ende ausgeführt oder die Schleife mit
next
unterbrochen wurde. Der continue
-Block wird nicht ausgeführt, wenn Sie die
Schleife mit last
oder redo
abbrechen. Nach der Ausführung des continue
-Blocks
macht Perl mit dem nächsten Schleifendurchlauf (also der Auswertung der
Schleifenbedingung) weiter.
Sie könnten einen continue
-Block zum Beispiel verwenden, um einen Fehler zu
beheben, der die Schleife unterbrochen hat. Danach läuft die Schleife normal weiter.
Das ganze sieht folgendermaßen aus (wobei die while
-Schleife hier auch eine for
-
oder foreach
-Schleife sein könnte):
while ( Bedingung ) {
# Anweisungen
if (noch eine Bedingung) {
# Fehler!
next; # (springt zum continue-Block)
}
# weitere Anweisungen
} continue {
# behebe Fehler
# (danach geht's am Schleifenanfang weiter)
}
Beachten Sie, dass
continue
in Perl etwas anderes ist als dascontinue
in C - dessen Perl-Pendant istnext
.
Bemerkenswerterweise hat Perl keine explizite switch
- (oder, je nach Ihrer
Lieblingssprache, auch case
-) Anweisung. Eine switch
-Anweisung (Schalter, Weiche)
ist eine Fallunterscheidung, mit der Sie einen Wert überprüfen, und viel kompakter,
übersichtlicher und oft auch effizienter als eine Konstruktion aus zahllosen if
und
elsif
, um festzulegen, was bei welchem Ergebnis geschehen soll.
Ein solches switch
-Konstrukt können Sie in Perl jedoch »nachahmen«. Hier erweisen
sich freistehende Blöcke (bare blocks) als äußerst nützlich. Für freistehende Blöcke
gelten die gleichen Regeln wie für Schleifenblöcke; deswegen können Sie sie mit
Labels versehen, mit redo
erneut ausführen oder mit last
und next
verlassen (next
führt einen eventuell vorhandenen continue
-Block aus, last
nicht). Das nutzen wir
aus und schreiben unser »Perl-Switch
« zum Beispiel so:
SWITCH: {
$a eq "eins" && do {
$a = 1;
last SWITCH;
};
$a eq "zwei" && do {
$a = 2;
last SWITCH;
};
$a eq "drei" && do {
$a = 3;
last SWITCH;
};
# und so weiter
}
Mit Pattern Matching (Mustervergleichen) geht es noch kürzer. Ich komme darauf zurück, wenn wir uns an Tag 9 mit Pattern Matching und regulären Ausdrücken befassen.
Ja, Perl unterstützt das verschriene goto
(englisch »gehe zu«). Es ist in keinem Fall
empfehlenswert, doch wenn Sie denn unbedingt wollen, können Sie goto
auf drei
Arten einsetzen:
goto LABEL
, wobei das Label irgendwo in Ihrem Skript stehen kann, in diesem
Fall also wirklich eine Sprungmarke ist.
goto AUSDRUCK
, wobei der Ausdruck zu einem Labelnamen ausgewertet werden
muss.
goto &NAME
, was nicht wirklich ein goto
ist - es ersetzt eine laufende Unterroutine
durch die Unterroutine &NAME
. Es wird beim automatischen Laden von
Subroutinen in Pakete genutzt.
Falls Sie sich für weitere goto
-Details interessieren, schauen Sie in die perlsyn-
Manpage.
Mit Bedingungen und Schleifen können Sie je nach Situation entscheiden, wie sich Ihr Skript verhalten soll. Diese Kontrollstrukturen sind so wichtig, dass wir schon seit dem zweiten Tag - lange bevor wir zu dieser Lektion gekommen sind - in den Beispielen mit ihnen gearbeitet haben.
Bedingungsanweisungen zweigen zu unterschiedlichen Anweisungsblöcken ab,
abhängig davon, ob eine Bedingung erfüllt ist oder nicht. Sie haben die Konstrukte if
,
if
...else
und if
...elsif
kennengelernt, den Bedingungsoperator (?
...:
), der in
andere Ausdrücke eingebaut werden kann, und Sie haben gesehen, wie Sie logische
Operatoren (&&
, ||
, and
und or
) zur Steuerung des Programmflusses einsetzen
können.
Als nächstes haben wir Schleifen behandelt: insbesondere die while
-Schleifen, die
einen Anweisungsblock so lange ausführen, wie die Schleifenbedingung wahr ist. Sie
haben das Gegenstück, die until
-Schleife, kennengelernt, die abbricht, wenn die
Bedingung wahr ist. In diesem Zusammenhang haben wir auch die do
-Schleifen
(do
...while
und do
...until
) und ihre Besonderheiten besprochen - beispielsweise dass
sie gar keine echten Schleifen sind.
Die zweite Art von Schleife ist die for
-Schleife, die ebenfalls Anweisungsblöcke
wiederholt, aber geeigneter ist, wenn die Anzahl der Durchläufe von vornherein
feststeht. Sie haben die for
-Schleife mit ihrer C-ähnlichen Zählersyntax und die
foreach
-Schleife zum Durchlaufen aller Elemente in einer Liste kennengelernt.
Dann habe ich erklärt, wie Sie mit den Schleifensteuerbefehlen next, last
und redo
die Ausführung eines Blocks abbrechen und Teile der Schleife überspringen sowie in
verschachtelten Schleifen eine bestimmte, mit einem Label versehene Schleife
ansteuern können.
Schließlich haben wir die Lektion wie die beiden letzten Lektionen mit weiteren
Anmerkungen zum Thema Eingabe beendet. Diesmal haben Sie gelernt, wie Sie mit
der
<>-Syntax aus Dateien lesen und mit der Variablen
$
_ viele Operationen
abkürzen können.
Mit der heutigen Lektion haben Sie sich die Grundlagen zu Perl vollständig angeeignet. Morgen werden wir die Woche mit einigen längeren Beispielen abschließen. Nächste Woche geht es nicht bloß weiter, nächste Woche legen wir richtig los und widmen uns einigen der mächtigsten und aufregendsten Perl-Features, darunter Pattern Matching (Mustervergleiche) und allerlei Listenakrobatik.
Sie haben heute folgende Perl-Funktionen kennengelernt:
do
, eine Funktion, die sich wie eine Schleife mit einem while
oder until
am
Ende benimmt
rand,
das eine Zufallszahl zwischen 0 und ihrem Argument generiert,
srand,
den von rand
verwendeten Zufallsgenerator initialisiert. Ohne Argument
nimmt srand
die aktuelle Uhrzeit als Startwert.
Mehr Details finden Sie - wie bereits erwähnt - in der perlfunc-Manpage.
Frage:
Ich habe den Eindruck, dass for
-Schleifen auch als while
-Schleifen geschrieben
werden könnten und umgekehrt.
Antwort:
Ja, das könnten sie mit Sicherheit. Aber es geht darum, was Sie sich
vorstellen, eine »wiederhole x-mal«- oder eine »wiederhole so lange, bis«-
Schleife. Sie sollen dann nicht noch darüber nachdenken müssen, wie Sie die
eine in die andere umformulieren. Es ist eine der angenehmsten
Eigenschaften von Perl, dass es in der Lage ist, sich an Ihre Denkweise
anzupassen. Übrigens kennen sogar die weit weniger »entgegenkommenden«
Sprachen C und Java while-
und for
-Schleifen.
Frage:
Ich habe versucht, mit continue
eine Schleife abzubrechen, und Perl hat mit
Fehlermeldungen um sich geworfen. Was habe ich falsch gemacht?
Antwort:
Sie haben vergessen, dass continue
in Perl nicht zum Abbrechen von
Schleifen verwendet wird (oder Sie haben diesen Abschnitt übersprungen).
Die Entsprechung zum continue
von C ist in Perl next
. Verwenden Sie
continue
nur als optionalen Anweisungsblock, der am Ende eines Blocks
ausgeführt werden soll (siehe Vertiefungsabschnitt).
Frage:
Ich habe mir Ihre beiden Beispiele für das Lesen aus Dateien angesehen. Eines
der Beispiele verwendet $_
, das andere nicht. Das erste ist zwar kürzer, aber wenn
man nicht weiß, welche Operationen mit $_
arbeiten, ist es kaum zu verstehen.
Ich finde, dass die Lesbarkeit eines Skripts ein paar mehr Buchstaben wert ist.
Antwort:
Das ist definitiv ein guter Grundsatz, dem auch viele Perl-Programmierer
folgen. »Schleichwege« über die $_
-Variable machen das Skript zwar kürzer,
aber bestimmt auch weniger verständlich. In manchen Fällen - wie zum
Beispiel für Eingaben mit <>
-
ist der Einsatz von $_
jedoch eine Art
weitverbreitete »Redensart«, die, sobald Sie sich daran gewöhnt haben, auch
vernünftig und verständlich scheint. Wenn Sie Skripts von anderen lesen oder
verändern müssen, werden Sie wahrscheinlich recht bald auf mysteriöses $_
-
Verhalten stoßen. Achten Sie also auf $_
, setzen Sie es ein, wo es wirklich
angemessen ist, oder vermeiden Sie es, wenn es Ihrer Meinung nach die
Lesbarkeit zu sehr einschränkt. Die Entscheidung liegt ganz bei Ihnen.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.
if
und unless
?
while
-Schleife abgebrochen?
do
-Schleifen anders als while-
oder until
-Schleifen?
for
-Schleife? Was machen sie?
next
, last
und redo
.
$_
verwendet werden kann.
<>
von <STDIN>
?
if ($wert == 4) then { print $wert; }
elseif ($wert > 4) { print "mehr als 4"; }
for ($i = 0, $i < $max, $i++) {
$werte[$i] = "";
}
while ($i < $max) {
$werte[$i] = 0;
}
namen.pl
so um, dass es die Namen aus einer Datei liest
(wenn Sie gestern die Übungsaufgabe 3 gelöst haben, also auch mit Zweitnamen
umgehen können, schreiben Sie diese Version entsprechend neu).
Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.
{}
) umgebene Gruppe von Perl-
Anweisungen (die andere Blöcke enthalten kann). Blöcke werden meistens im
Zusammenhang mit Bedingungen und Schleifen gebraucht, können aber auch
allein, als freistehende Blöcke, verwendet werden (bare blocks).
if
-Anweisung führt ihren Block aus, wenn der Bedingungsausdruck zu wahr
ausgewertet wird, unless
führt ihn aus, wenn die Bedingung falsch ergibt.
while
-Schleife bricht ab, wenn ihr Bedingungsausdruck falsch zurückgibt.
do
-Schleifen unterscheiden sich in zweierlei Hinsicht von while-
oder until
-
Schleifen. Zum einen werden ihre Blöcke vor der ersten Überprüfung, also
mindestens einmal, ausgeführt. Zum anderen können Sie keine
Schleifensteuerbefehle wie last
oder next
in einer do
-Schleife verwenden, weil do
gar keine echte Schleife, sondern eigentlich eine Funktion mit einem Modifikator
ist.
for
-Schleife sind:
$i = 0.
$i < $max.
$i++.
next
, last
und redo
sind Schleifensteuerbefehele. next
bricht den aktuellen
Durchlauf ab und startet am Schleifenanfang den nächsten, inklusive der
Überprüfung der Bedingung in einer for
- oder while
-Schleife. Auch redo
bricht
den aktuellen Durchlauf ab, startet aber am Anfang des Blocks, ohne die
Schleifenbedingung zu überprüfen. last
beendet die Schleife ebenso komplett wie
abrupt - es prüft nichts, startet nichts, es steigt einfach aus.
$_
verwendet, wenn Sie keine Variable
angegeben:
while (<>)
weist $_
jede Zeile einzeln zu.
chomp
entfernt den Zeilenvorschub vom String in $_.
foreach
verwendet $_
als temporäre Schleifenvariable.
<>
wird verwendet, um die Inhalte von Dateien einzulesen, die über die
Kommandozeile angegeben (und in @
ARGV gespeichert) werden. Mit <STDIN>
liest man Daten von der Standardeingabe (normalerweise der Tastatur) ein.
#!/usr/bin/perl -w
$zahl1 = 0;
$zahl2 = 0;
while () {
print 'Geben Sie die erste Zahl ein: ';
chomp($zahl1 = <STDIN>);
print 'Geben Sie die zweite Zahl ein: ';
chomp($zahl2 = <STDIN>);
if ($zahl1 < 0 || $zahl2 < 0) {
print "Keine negativen Zahlen!\n";
next;
} elsif ( $zahl2 == 0) {
print "Die zweite Zahl darf nicht 0 sein!\n";
next;
} else { last; }
}
print "$zahl1 geteilt durch $zahl2 ist ";
printf("%.2f\n", $zahl1 / $zahl2 );
elseif
ist kein gültiges Perl-Schlüsselwort, nehmen Sie statt
dessen elsif
. then
ist ebenfalls kein gültiges Perl-Schlüsselwort.
for
-Bedingungsausdrucks müssen mit Semikola,
nicht mit Kommata getrennt werden.
$i
wird innerhalb
der Schleife nicht inkrementiert.
#!/usr/bin/perl -w
%names = (); # Hash: Namen
@raw = (); # temp: rohe Woerter
$fn = ''; # Vorname
while (<>) {
chomp;
@raw = split(" ", $_);
if ($#raw == 1) { # Normalfall: zwei Woerter
$names{$raw[1]} = $raw[0];
} else { # den Vornamen zusammensetzen
$fn = '';
for ($i = 0; $i < $#raw; $i++) {
$fn .= $raw[$i] . " ";
}
$names{$raw[$#raw]} = $fn;
}
}
foreach $lastname (sort keys %names) {
print "$lastname, $names{$lastname}\n";
}